// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "cc/test/test_web_graphics_context_3d.h"

#include <stddef.h>
#include <stdint.h>

#include <algorithm>
#include <string>

#include "base/bind.h"
#include "base/lazy_instance.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/message_loop/message_loop.h"
#include "base/numerics/safe_conversions.h"
#include "cc/test/test_context_support.h"
#include "gpu/GLES2/gl2extchromium.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/khronos/GLES2/gl2ext.h"

namespace cc {

static unsigned s_context_id = 1;

const GLuint TestWebGraphicsContext3D::kExternalTextureId = 1337;

static base::LazyInstance<base::Lock>::Leaky
    g_shared_namespace_lock
    = LAZY_INSTANCE_INITIALIZER;

TestWebGraphicsContext3D::Namespace*
    TestWebGraphicsContext3D::shared_namespace_
    = NULL;

TestWebGraphicsContext3D::Namespace::Namespace()
    : next_buffer_id(1)
    , next_image_id(1)
    , next_texture_id(1)
    , next_renderbuffer_id(1)
{
}

TestWebGraphicsContext3D::Namespace::~Namespace()
{
    g_shared_namespace_lock.Get().AssertAcquired();
    if (shared_namespace_ == this)
        shared_namespace_ = NULL;
}

// static
std::unique_ptr<TestWebGraphicsContext3D> TestWebGraphicsContext3D::Create()
{
    return base::WrapUnique(new TestWebGraphicsContext3D());
}

TestWebGraphicsContext3D::TestWebGraphicsContext3D()
    : context_id_(s_context_id++)
    , times_bind_texture_succeeds_(-1)
    , times_end_query_succeeds_(-1)
    , context_lost_(false)
    , times_map_buffer_chromium_succeeds_(-1)
    , next_program_id_(1000)
    , next_shader_id_(2000)
    , next_framebuffer_id_(1)
    , current_framebuffer_(0)
    , max_texture_size_(2048)
    , reshape_called_(false)
    , width_(0)
    , height_(0)
    , scale_factor_(-1.f)
    , test_support_(NULL)
    , last_update_type_(NO_UPDATE)
    , next_insert_fence_sync_(1)
    , unpack_alignment_(4)
    , bound_buffer_(0)
    , weak_ptr_factory_(this)
{
    CreateNamespace();
    set_have_extension_egl_image(true); // For stream textures.
}

TestWebGraphicsContext3D::~TestWebGraphicsContext3D()
{
    base::AutoLock lock(g_shared_namespace_lock.Get());
    namespace_ = NULL;
}

void TestWebGraphicsContext3D::CreateNamespace()
{
    base::AutoLock lock(g_shared_namespace_lock.Get());
    if (shared_namespace_) {
        namespace_ = shared_namespace_;
    } else {
        namespace_ = new Namespace;
        shared_namespace_ = namespace_.get();
    }
}

void TestWebGraphicsContext3D::reshapeWithScaleFactor(
    int width, int height, float scale_factor)
{
    reshape_called_ = true;
    width_ = width;
    height_ = height;
    scale_factor_ = scale_factor;
}

bool TestWebGraphicsContext3D::isContextLost()
{
    return context_lost_;
}

GLenum TestWebGraphicsContext3D::checkFramebufferStatus(
    GLenum target)
{
    if (context_lost_)
        return GL_FRAMEBUFFER_UNDEFINED_OES;
    return GL_FRAMEBUFFER_COMPLETE;
}

GLint TestWebGraphicsContext3D::getUniformLocation(
    GLuint program,
    const GLchar* name)
{
    return 0;
}

GLsizeiptr TestWebGraphicsContext3D::getVertexAttribOffset(
    GLuint index,
    GLenum pname)
{
    return 0;
}

GLboolean TestWebGraphicsContext3D::isBuffer(
    GLuint buffer)
{
    return false;
}

GLboolean TestWebGraphicsContext3D::isEnabled(
    GLenum cap)
{
    return false;
}

GLboolean TestWebGraphicsContext3D::isFramebuffer(
    GLuint framebuffer)
{
    return false;
}

GLboolean TestWebGraphicsContext3D::isProgram(
    GLuint program)
{
    return false;
}

GLboolean TestWebGraphicsContext3D::isRenderbuffer(
    GLuint renderbuffer)
{
    return false;
}

GLboolean TestWebGraphicsContext3D::isShader(
    GLuint shader)
{
    return false;
}

GLboolean TestWebGraphicsContext3D::isTexture(
    GLuint texture)
{
    return false;
}

void TestWebGraphicsContext3D::genBuffers(GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i)
        ids[i] = NextBufferId();
}

void TestWebGraphicsContext3D::genFramebuffers(
    GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i)
        ids[i] = NextFramebufferId();
}

void TestWebGraphicsContext3D::genRenderbuffers(
    GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i)
        ids[i] = NextRenderbufferId();
}

void TestWebGraphicsContext3D::genTextures(GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i) {
        ids[i] = NextTextureId();
        DCHECK_NE(ids[i], kExternalTextureId);
    }
    base::AutoLock lock(namespace_->lock);
    for (int i = 0; i < count; ++i)
        namespace_->textures.Append(ids[i], new TestTexture());
}

void TestWebGraphicsContext3D::deleteBuffers(GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i)
        RetireBufferId(ids[i]);
}

void TestWebGraphicsContext3D::deleteFramebuffers(
    GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i) {
        if (ids[i]) {
            RetireFramebufferId(ids[i]);
            if (ids[i] == current_framebuffer_)
                current_framebuffer_ = 0;
        }
    }
}

void TestWebGraphicsContext3D::deleteRenderbuffers(
    GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i)
        RetireRenderbufferId(ids[i]);
}

void TestWebGraphicsContext3D::deleteTextures(GLsizei count, GLuint* ids)
{
    for (int i = 0; i < count; ++i)
        RetireTextureId(ids[i]);
    base::AutoLock lock(namespace_->lock);
    for (int i = 0; i < count; ++i) {
        namespace_->textures.Remove(ids[i]);
        texture_targets_.UnbindTexture(ids[i]);
    }
}

GLuint TestWebGraphicsContext3D::createBuffer()
{
    GLuint id;
    genBuffers(1, &id);
    return id;
}

GLuint TestWebGraphicsContext3D::createFramebuffer()
{
    GLuint id;
    genFramebuffers(1, &id);
    return id;
}

GLuint TestWebGraphicsContext3D::createRenderbuffer()
{
    GLuint id;
    genRenderbuffers(1, &id);
    return id;
}

GLuint TestWebGraphicsContext3D::createTexture()
{
    GLuint id;
    genTextures(1, &id);
    return id;
}

void TestWebGraphicsContext3D::deleteBuffer(GLuint id)
{
    deleteBuffers(1, &id);
}

void TestWebGraphicsContext3D::deleteFramebuffer(GLuint id)
{
    deleteFramebuffers(1, &id);
}

void TestWebGraphicsContext3D::deleteRenderbuffer(GLuint id)
{
    deleteRenderbuffers(1, &id);
}

void TestWebGraphicsContext3D::deleteTexture(GLuint id)
{
    deleteTextures(1, &id);
}

unsigned TestWebGraphicsContext3D::createProgram()
{
    unsigned program = next_program_id_++ | context_id_ << 16;
    program_set_.insert(program);
    return program;
}

GLuint TestWebGraphicsContext3D::createShader(GLenum)
{
    unsigned shader = next_shader_id_++ | context_id_ << 16;
    shader_set_.insert(shader);
    return shader;
}

GLuint TestWebGraphicsContext3D::createExternalTexture()
{
    base::AutoLock lock(namespace_->lock);
    namespace_->textures.Append(kExternalTextureId, new TestTexture());
    return kExternalTextureId;
}

void TestWebGraphicsContext3D::deleteProgram(GLuint id)
{
    if (!program_set_.count(id))
        ADD_FAILURE() << "deleteProgram called on unknown program " << id;
    program_set_.erase(id);
}

void TestWebGraphicsContext3D::deleteShader(GLuint id)
{
    if (!shader_set_.count(id))
        ADD_FAILURE() << "deleteShader called on unknown shader " << id;
    shader_set_.erase(id);
}

void TestWebGraphicsContext3D::attachShader(GLuint program, GLuint shader)
{
    if (!program_set_.count(program))
        ADD_FAILURE() << "attachShader called with unknown program " << program;
    if (!shader_set_.count(shader))
        ADD_FAILURE() << "attachShader called with unknown shader " << shader;
}

void TestWebGraphicsContext3D::useProgram(GLuint program)
{
    if (!program)
        return;
    if (!program_set_.count(program))
        ADD_FAILURE() << "useProgram called on unknown program " << program;
}

void TestWebGraphicsContext3D::bindFramebuffer(
    GLenum target, GLuint framebuffer)
{
    base::AutoLock lock_for_framebuffer_access(namespace_->lock);
    if (framebuffer != 0 && framebuffer_set_.find(framebuffer) == framebuffer_set_.end()) {
        ADD_FAILURE() << "bindFramebuffer called with unknown framebuffer";
    } else if (framebuffer != 0 && (framebuffer >> 16) != context_id_) {
        ADD_FAILURE()
            << "bindFramebuffer called with framebuffer from other context";
    } else {
        current_framebuffer_ = framebuffer;
    }
}

void TestWebGraphicsContext3D::bindRenderbuffer(
    GLenum target, GLuint renderbuffer)
{
    if (!renderbuffer)
        return;
    base::AutoLock lock_for_renderbuffer_access(namespace_->lock);
    if (renderbuffer != 0 && namespace_->renderbuffer_set.find(renderbuffer) == namespace_->renderbuffer_set.end()) {
        ADD_FAILURE() << "bindRenderbuffer called with unknown renderbuffer";
    } else if ((renderbuffer >> 16) != context_id_) {
        ADD_FAILURE()
            << "bindRenderbuffer called with renderbuffer from other context";
    }
}

void TestWebGraphicsContext3D::bindTexture(
    GLenum target, GLuint texture_id)
{
    if (times_bind_texture_succeeds_ >= 0) {
        if (!times_bind_texture_succeeds_) {
            loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
                GL_INNOCENT_CONTEXT_RESET_ARB);
        }
        --times_bind_texture_succeeds_;
    }

    if (!texture_id)
        return;
    base::AutoLock lock(namespace_->lock);
    DCHECK(namespace_->textures.ContainsId(texture_id));
    texture_targets_.BindTexture(target, texture_id);
    used_textures_.insert(texture_id);
}

GLuint TestWebGraphicsContext3D::BoundTextureId(
    GLenum target)
{
    return texture_targets_.BoundTexture(target);
}

scoped_refptr<TestTexture> TestWebGraphicsContext3D::BoundTexture(
    GLenum target)
{
    // The caller is expected to lock the namespace for texture access.
    namespace_->lock.AssertAcquired();
    return namespace_->textures.TextureForId(BoundTextureId(target));
}

scoped_refptr<TestTexture> TestWebGraphicsContext3D::UnboundTexture(
    GLuint texture)
{
    // The caller is expected to lock the namespace for texture access.
    namespace_->lock.AssertAcquired();
    return namespace_->textures.TextureForId(texture);
}

void TestWebGraphicsContext3D::CheckTextureIsBound(GLenum target)
{
    DCHECK(BoundTextureId(target));
}

GLuint TestWebGraphicsContext3D::createQueryEXT() { return 1u; }

void TestWebGraphicsContext3D::endQueryEXT(GLenum target)
{
    if (times_end_query_succeeds_ >= 0) {
        if (!times_end_query_succeeds_) {
            loseContextCHROMIUM(GL_GUILTY_CONTEXT_RESET_ARB,
                GL_INNOCENT_CONTEXT_RESET_ARB);
        }
        --times_end_query_succeeds_;
    }
}

void TestWebGraphicsContext3D::getQueryObjectuivEXT(
    GLuint query,
    GLenum pname,
    GLuint* params)
{
    // If the context is lost, behave as if result is available.
    if (pname == GL_QUERY_RESULT_AVAILABLE_EXT)
        *params = 1;
}

void TestWebGraphicsContext3D::getIntegerv(
    GLenum pname,
    GLint* value)
{
    if (pname == GL_MAX_TEXTURE_SIZE)
        *value = max_texture_size_;
    else if (pname == GL_ACTIVE_TEXTURE)
        *value = GL_TEXTURE0;
    else if (pname == GL_UNPACK_ALIGNMENT)
        *value = unpack_alignment_;
    else if (pname == GL_FRAMEBUFFER_BINDING)
        *value = current_framebuffer_;
}

void TestWebGraphicsContext3D::getProgramiv(GLuint program,
    GLenum pname,
    GLint* value)
{
    if (pname == GL_LINK_STATUS)
        *value = 1;
}

void TestWebGraphicsContext3D::getShaderiv(GLuint shader,
    GLenum pname,
    GLint* value)
{
    if (pname == GL_COMPILE_STATUS)
        *value = 1;
}

void TestWebGraphicsContext3D::getShaderPrecisionFormat(GLenum shadertype,
    GLenum precisiontype,
    GLint* range,
    GLint* precision)
{
    // Return the minimum precision requirements of the GLES2
    // specification.
    switch (precisiontype) {
    case GL_LOW_INT:
        range[0] = 8;
        range[1] = 8;
        *precision = 0;
        break;
    case GL_MEDIUM_INT:
        range[0] = 10;
        range[1] = 10;
        *precision = 0;
        break;
    case GL_HIGH_INT:
        range[0] = 16;
        range[1] = 16;
        *precision = 0;
        break;
    case GL_LOW_FLOAT:
        range[0] = 8;
        range[1] = 8;
        *precision = 8;
        break;
    case GL_MEDIUM_FLOAT:
        range[0] = 14;
        range[1] = 14;
        *precision = 10;
        break;
    case GL_HIGH_FLOAT:
        range[0] = 62;
        range[1] = 62;
        *precision = 16;
        break;
    default:
        NOTREACHED();
        break;
    }
}

void TestWebGraphicsContext3D::genMailboxCHROMIUM(GLbyte* mailbox)
{
    static char mailbox_name1 = '1';
    static char mailbox_name2 = '1';
    mailbox[0] = mailbox_name1;
    mailbox[1] = mailbox_name2;
    mailbox[2] = '\0';
    if (++mailbox_name1 == 0) {
        mailbox_name1 = '1';
        ++mailbox_name2;
    }
}

GLuint TestWebGraphicsContext3D::createAndConsumeTextureCHROMIUM(
    GLenum target,
    const GLbyte* mailbox)
{
    GLuint texture_id = createTexture();
    consumeTextureCHROMIUM(target, mailbox);
    return texture_id;
}

void TestWebGraphicsContext3D::loseContextCHROMIUM(GLenum current,
    GLenum other)
{
    if (context_lost_)
        return;
    context_lost_ = true;
    if (!context_lost_callback_.is_null())
        context_lost_callback_.Run();

    for (size_t i = 0; i < shared_contexts_.size(); ++i)
        shared_contexts_[i]->loseContextCHROMIUM(current, other);
    shared_contexts_.clear();
}

void TestWebGraphicsContext3D::finish()
{
    test_support_->CallAllSyncPointCallbacks();
}

void TestWebGraphicsContext3D::flush()
{
    test_support_->CallAllSyncPointCallbacks();
}

void TestWebGraphicsContext3D::shallowFinishCHROMIUM()
{
    test_support_->CallAllSyncPointCallbacks();
}

GLint TestWebGraphicsContext3D::getAttribLocation(GLuint program,
    const GLchar* name)
{
    return 0;
}

GLenum TestWebGraphicsContext3D::getError() { return GL_NO_ERROR; }

void TestWebGraphicsContext3D::bindBuffer(GLenum target,
    GLuint buffer)
{
    bound_buffer_ = buffer;
    if (!bound_buffer_)
        return;
    unsigned context_id = buffer >> 16;
    unsigned buffer_id = buffer & 0xffff;
    base::AutoLock lock(namespace_->lock);
    DCHECK(buffer_id);
    DCHECK_LT(buffer_id, namespace_->next_buffer_id);
    DCHECK_EQ(context_id, context_id_);

    std::unordered_map<unsigned, std::unique_ptr<Buffer>>& buffers = namespace_->buffers;
    if (buffers.count(bound_buffer_) == 0)
        buffers[bound_buffer_] = base::WrapUnique(new Buffer);

    buffers[bound_buffer_]->target = target;
}

void TestWebGraphicsContext3D::bufferData(GLenum target,
    GLsizeiptr size,
    const void* data,
    GLenum usage)
{
    base::AutoLock lock(namespace_->lock);
    std::unordered_map<unsigned, std::unique_ptr<Buffer>>& buffers = namespace_->buffers;
    DCHECK_GT(buffers.count(bound_buffer_), 0u);
    DCHECK_EQ(target, buffers[bound_buffer_]->target);
    Buffer* buffer = buffers[bound_buffer_].get();
    if (context_lost_) {
        buffer->pixels = nullptr;
        return;
    }

    buffer->pixels.reset(new uint8_t[size]);
    buffer->size = size;
    if (data != nullptr)
        memcpy(buffer->pixels.get(), data, size);
}

void TestWebGraphicsContext3D::pixelStorei(GLenum pname, GLint param)
{
    switch (pname) {
    case GL_UNPACK_ALIGNMENT:
        // Param should be a power of two <= 8.
        EXPECT_EQ(0, param & (param - 1));
        EXPECT_GE(8, param);
        switch (param) {
        case 1:
        case 2:
        case 4:
        case 8:
            unpack_alignment_ = param;
            break;
        default:
            break;
        }
        break;
    default:
        break;
    }
}

void* TestWebGraphicsContext3D::mapBufferCHROMIUM(GLenum target,
    GLenum access)
{
    base::AutoLock lock(namespace_->lock);
    std::unordered_map<unsigned, std::unique_ptr<Buffer>>& buffers = namespace_->buffers;
    DCHECK_GT(buffers.count(bound_buffer_), 0u);
    DCHECK_EQ(target, buffers[bound_buffer_]->target);
    if (times_map_buffer_chromium_succeeds_ >= 0) {
        if (!times_map_buffer_chromium_succeeds_) {
            return NULL;
        }
        --times_map_buffer_chromium_succeeds_;
    }

    return buffers[bound_buffer_]->pixels.get();
}

GLboolean TestWebGraphicsContext3D::unmapBufferCHROMIUM(
    GLenum target)
{
    base::AutoLock lock(namespace_->lock);
    std::unordered_map<unsigned, std::unique_ptr<Buffer>>& buffers = namespace_->buffers;
    DCHECK_GT(buffers.count(bound_buffer_), 0u);
    DCHECK_EQ(target, buffers[bound_buffer_]->target);
    buffers[bound_buffer_]->pixels = nullptr;
    return true;
}

GLuint TestWebGraphicsContext3D::createImageCHROMIUM(ClientBuffer buffer,
    GLsizei width,
    GLsizei height,
    GLenum internalformat)
{
    DCHECK(internalformat == GL_RGB || internalformat == GL_RGBA);
    GLuint image_id = NextImageId();
    base::AutoLock lock(namespace_->lock);
    std::unordered_set<unsigned>& images = namespace_->images;
    images.insert(image_id);
    return image_id;
}

void TestWebGraphicsContext3D::destroyImageCHROMIUM(
    GLuint id)
{
    RetireImageId(id);
    base::AutoLock lock(namespace_->lock);
    std::unordered_set<unsigned>& images = namespace_->images;
    if (!images.count(id))
        ADD_FAILURE() << "destroyImageCHROMIUM called on unknown image " << id;
    images.erase(id);
}

GLuint TestWebGraphicsContext3D::createGpuMemoryBufferImageCHROMIUM(
    GLsizei width,
    GLsizei height,
    GLenum internalformat,
    GLenum usage)
{
    DCHECK(internalformat == GL_RGB || internalformat == GL_RGBA);
    GLuint image_id = NextImageId();
    base::AutoLock lock(namespace_->lock);
    std::unordered_set<unsigned>& images = namespace_->images;
    images.insert(image_id);
    return image_id;
}

GLuint64 TestWebGraphicsContext3D::insertFenceSync()
{
    return next_insert_fence_sync_++;
}

void TestWebGraphicsContext3D::genSyncToken(GLuint64 fence_sync,
    GLbyte* sync_token)
{
    // Don't return a valid sync token if context is lost. This matches behavior
    // of CommandBufferProxyImpl.
    if (context_lost_)
        return;
    gpu::SyncToken sync_token_data(gpu::CommandBufferNamespace::GPU_IO, 0,
        gpu::CommandBufferId(), fence_sync);
    sync_token_data.SetVerifyFlush();
    memcpy(sync_token, &sync_token_data, sizeof(sync_token_data));
}

void TestWebGraphicsContext3D::waitSyncToken(const GLbyte* sync_token)
{
    if (sync_token) {
        gpu::SyncToken sync_token_data;
        memcpy(sync_token_data.GetData(), sync_token, sizeof(sync_token_data));
        if (sync_token_data.HasData())
            last_waited_sync_token_ = sync_token_data;
    }
}

void TestWebGraphicsContext3D::verifySyncTokens(GLbyte** sync_tokens,
    GLsizei count)
{
    for (GLsizei i = 0; i < count; ++i) {
        gpu::SyncToken sync_token_data;
        memcpy(sync_token_data.GetData(), sync_tokens[i], sizeof(sync_token_data));
        sync_token_data.SetVerifyFlush();
        memcpy(sync_tokens[i], &sync_token_data, sizeof(sync_token_data));
    }
}

size_t TestWebGraphicsContext3D::NumTextures() const
{
    base::AutoLock lock(namespace_->lock);
    return namespace_->textures.Size();
}

GLuint TestWebGraphicsContext3D::TextureAt(int i) const
{
    base::AutoLock lock(namespace_->lock);
    return namespace_->textures.IdAt(i);
}

GLuint TestWebGraphicsContext3D::NextTextureId()
{
    base::AutoLock lock(namespace_->lock);
    GLuint texture_id = namespace_->next_texture_id++;
    DCHECK(texture_id < (1 << 16));
    texture_id |= context_id_ << 16;
    return texture_id;
}

void TestWebGraphicsContext3D::RetireTextureId(GLuint id)
{
    base::AutoLock lock(namespace_->lock);
    unsigned context_id = id >> 16;
    unsigned texture_id = id & 0xffff;
    DCHECK(texture_id);
    DCHECK_LT(texture_id, namespace_->next_texture_id);
    DCHECK_EQ(context_id, context_id_);
}

GLuint TestWebGraphicsContext3D::NextBufferId()
{
    base::AutoLock lock(namespace_->lock);
    GLuint buffer_id = namespace_->next_buffer_id++;
    DCHECK(buffer_id < (1 << 16));
    buffer_id |= context_id_ << 16;
    return buffer_id;
}

void TestWebGraphicsContext3D::RetireBufferId(GLuint id)
{
    base::AutoLock lock(namespace_->lock);
    unsigned context_id = id >> 16;
    unsigned buffer_id = id & 0xffff;
    DCHECK(buffer_id);
    DCHECK_LT(buffer_id, namespace_->next_buffer_id);
    DCHECK_EQ(context_id, context_id_);
}

GLuint TestWebGraphicsContext3D::NextImageId()
{
    base::AutoLock lock(namespace_->lock);
    GLuint image_id = namespace_->next_image_id++;
    DCHECK(image_id < (1 << 16));
    image_id |= context_id_ << 16;
    return image_id;
}

void TestWebGraphicsContext3D::RetireImageId(GLuint id)
{
    base::AutoLock lock(namespace_->lock);
    unsigned context_id = id >> 16;
    unsigned image_id = id & 0xffff;
    DCHECK(image_id);
    DCHECK_LT(image_id, namespace_->next_image_id);
    DCHECK_EQ(context_id, context_id_);
}

GLuint TestWebGraphicsContext3D::NextFramebufferId()
{
    base::AutoLock lock_for_framebuffer_access(namespace_->lock);
    GLuint id = next_framebuffer_id_++;
    DCHECK(id < (1 << 16));
    id |= context_id_ << 16;
    framebuffer_set_.insert(id);
    return id;
}

void TestWebGraphicsContext3D::RetireFramebufferId(GLuint id)
{
    base::AutoLock lock_for_framebuffer_access(namespace_->lock);
    DCHECK(framebuffer_set_.find(id) != framebuffer_set_.end());
    framebuffer_set_.erase(id);
}

GLuint TestWebGraphicsContext3D::NextRenderbufferId()
{
    base::AutoLock lock_for_renderbuffer_access(namespace_->lock);
    GLuint id = namespace_->next_renderbuffer_id++;
    DCHECK(id < (1 << 16));
    id |= context_id_ << 16;
    namespace_->renderbuffer_set.insert(id);
    return id;
}

void TestWebGraphicsContext3D::RetireRenderbufferId(GLuint id)
{
    base::AutoLock lock_for_renderbuffer_access(namespace_->lock);
    DCHECK(namespace_->renderbuffer_set.find(id) != namespace_->renderbuffer_set.end());
    namespace_->renderbuffer_set.erase(id);
}

void TestWebGraphicsContext3D::SetMaxSamples(int max_samples)
{
    test_capabilities_.max_samples = max_samples;
}

TestWebGraphicsContext3D::TextureTargets::TextureTargets()
{
    // Initialize default bindings.
    bound_textures_[GL_TEXTURE_2D] = 0;
    bound_textures_[GL_TEXTURE_EXTERNAL_OES] = 0;
    bound_textures_[GL_TEXTURE_RECTANGLE_ARB] = 0;
}

TestWebGraphicsContext3D::TextureTargets::~TextureTargets() { }

void TestWebGraphicsContext3D::TextureTargets::BindTexture(
    GLenum target,
    GLuint id)
{
    // Make sure this is a supported target by seeing if it was bound to before.
    DCHECK(bound_textures_.find(target) != bound_textures_.end());
    bound_textures_[target] = id;
}

void TestWebGraphicsContext3D::texParameteri(GLenum target,
    GLenum pname,
    GLint param)
{
    CheckTextureIsBound(target);
    base::AutoLock lock_for_texture_access(namespace_->lock);
    scoped_refptr<TestTexture> texture = BoundTexture(target);
    DCHECK(texture->IsValidParameter(pname));
    texture->params[pname] = param;
}

void TestWebGraphicsContext3D::getTexParameteriv(GLenum target,
    GLenum pname,
    GLint* value)
{
    CheckTextureIsBound(target);
    base::AutoLock lock_for_texture_access(namespace_->lock);
    scoped_refptr<TestTexture> texture = BoundTexture(target);
    DCHECK(texture->IsValidParameter(pname));
    TestTexture::TextureParametersMap::iterator it = texture->params.find(pname);
    if (it != texture->params.end())
        *value = it->second;
}

void TestWebGraphicsContext3D::TextureTargets::UnbindTexture(
    GLuint id)
{
    // Bind zero to any targets that the id is bound to.
    for (TargetTextureMap::iterator it = bound_textures_.begin();
         it != bound_textures_.end();
         it++) {
        if (it->second == id)
            it->second = 0;
    }
}

GLuint TestWebGraphicsContext3D::TextureTargets::BoundTexture(
    GLenum target)
{
    DCHECK(bound_textures_.find(target) != bound_textures_.end());
    return bound_textures_[target];
}

TestWebGraphicsContext3D::Buffer::Buffer()
    : target(0)
    , size(0)
{
}

TestWebGraphicsContext3D::Buffer::~Buffer() { }

TestWebGraphicsContext3D::Image::Image() { }

TestWebGraphicsContext3D::Image::~Image() { }

} // namespace cc
